﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the Microsoft Public License (Ms-PL).
// See accompanying file License.txt or copy at http://mews.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Mews.Mime
{
    using System;

    /// <summary>Represents a single address (see <a href="http://www.ietf.org/rfc/rfc5322.txt">RFC
    /// 5322</a> 3.4.).</summary>
    /// <threadsafety static="true" instance="false"/>
    public sealed class Address : IEquatable<Address>
    {
        private readonly string displayName;
        private readonly string emailAddress;
        private readonly bool isEmptyGroup;

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>Initializes a new instance of the <see cref="Address"/> class that represents a mailbox.</summary>
        /// <param name="displayName">The unquoted display name of the mailbox. May be <c>null</c> or empty.</param>
        /// <param name="emailAddress">The email address without angle brackets. May be <c>null</c> or empty.</param>
        /// <exception cref="ArgumentException"><paramref name="displayName"/> and <paramref name="emailAddress"/> are
        /// both either <c>null</c> or empty.</exception>
        public Address(string displayName, string emailAddress)
        {
            if (string.IsNullOrEmpty(displayName) && string.IsNullOrEmpty(emailAddress))
            {
                throw new ArgumentException("displayName and emailAddress are both null or empty.");
            }

            this.displayName = displayName;
            this.emailAddress = emailAddress;
        }

        /// <summary>Initializes a new instance of the <see cref="Address"/> class that represents an empty group.
        /// </summary>
        /// <param name="groupName">The unquoted name of the empty group.</param>
        /// <exception cref="ArgumentException"><paramref name="groupName"/> is <c>null</c> or empty.</exception>
        public Address(string groupName)
        {
            if (string.IsNullOrEmpty(groupName))
            {
                throw new ArgumentException("Must not be null or empty.", "groupName");
            }

            this.displayName = groupName;
            this.isEmptyGroup = true;
        }

        /// <summary>Gets the display name.</summary>
        /// <value>The name of a group if this address represents an empty group; otherwise, the display name of the
        /// mailbox.</value>
        /// <remarks>May be <c>null</c> or empty.</remarks>
        public string DisplayName
        {
            get { return this.displayName; }
        }

        /// <summary>Gets the email address.</summary>
        /// <remarks>May be <c>null</c> or empty.</remarks>
        public string EmailAddress
        {
            get { return this.emailAddress; }
        }

        /// <summary>See <see cref="IEquatable{T}.Equals"/>.</summary>
        public bool Equals(Address other)
        {
            return (other != null) && (this.displayName == other.displayName) &&
                (this.emailAddress == other.emailAddress) && (this.isEmptyGroup == other.isEmptyGroup);
        }

        /// <summary>See <see cref="object.Equals(object)"/>.</summary>
        public sealed override bool Equals(object obj)
        {
            return this.Equals(obj as Address);
        }

        /// <summary>See <see cref="object.GetHashCode()"/>.</summary>
        public sealed override int GetHashCode()
        {
            return (this.displayName == null ? 0 : this.displayName.GetHashCode()) ^
                (this.emailAddress == null ? 0 : this.emailAddress.GetHashCode()) ^ this.isEmptyGroup.GetHashCode();
        }

        /// <summary>See <see cref="object.ToString"/>.</summary>
        /// <returns><see cref="DisplayName"/> if it is not <c>null</c> or empty; otherwise <see cref="EmailAddress"/>.
        /// </returns>
        public sealed override string ToString()
        {
            return string.IsNullOrEmpty(this.displayName) ? this.emailAddress : this.displayName;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        internal void WriteTo(HeaderFieldBodyPart[] parts, int offset)
        {
            var terminator = offset == parts.Length - 2 ? string.Empty : ", ";

            if (this.isEmptyGroup)
            {
                parts[offset] = new HeaderFieldBodyPart(this.displayName, true);
                parts[offset + 1] = new HeaderFieldBodyPart(":;" + terminator, false);
            }
            else
            {
                parts[offset] = new HeaderFieldBodyPart(this.displayName, true);
                parts[offset + 1] = new HeaderFieldBodyPart(
                    (string.IsNullOrEmpty(this.displayName) ? "<" : " <") + this.emailAddress + ">" + terminator,
                    false);
            }
        }
    }
}
